﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Web;
using System.Threading;
using System.Web.Hosting;

namespace nwrole.lib
{
    /// <summary>
    /// ErrorHelper
    /// Version 2012-11-06 Douglas Nordfeldt
    /// 
    ///  
    /// These keys are optional in config file:
    ///     <add key="SiteName" value="Site name"/>
    ///     <add key="EmailError" value="name1@domain.com,name2@domain.com"/> The emails to send the error to. Separetad by comma (,).
    ///     <add key="ErrorLogToFiles" value="false"/> If to save errors to file.
    ///     <add key="ErrorLogFilesPath" value="~/App_Data/Errors"/> The path to save the error logs. Can be a virtual (in website) or physical path.
    /// </summary>
    public static class ErrorHelper
    {
        private const string Version = "2012-11-06";
        private const string ERROR_FILE_PATH = "~/App_Data/Errors";

        public static void LogError(Exception exception)
        {
            LogError(exception, new string[0], string.Empty);
        }
        public static void LogError(Exception exception, string extrainfo)
        {
            LogError(exception, new string[0], extrainfo);
        }
        public static void LogError(Exception exception, string emails, string extraInfo)
        {
            LogError(exception, new string[] { emails }, extraInfo);
        }
        public static void LogError(Exception exception, string[] emails)
        {
            LogError(exception, emails, string.Empty);
        }
        public static void LogError(Exception exception, string[] emails, string extrainfo)
        {
            StringBuilder body = new StringBuilder();

            try
            {
                if (exception == null && IsInWebContext)
                    exception = HttpContext.Current.Server.GetLastError();

                if (exception == null)
                    return;

                body.AppendLine("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\">");
                body.AppendLine("<head><title>" + DateTime.Now.ToString("yyyy-MM-dd HH:mmffff") + "</title><meta content=\"text/html;charset=UTF-8\" http-equiv=\"Content-Type\"><style type=\"text/css\">#errorContainer {width:100%;margin-top:10px;padding:5px;background:#ffffcc;}#errorContainer div {margin:5px 0,padding:5px;}#errorContainer div, #errorContainer table{font-family:Tahoma;font-size:12px;background:#ffffcc;border:none;width:100%;border-collapse:collapse}#errorContainer table td{vertical-align:top;}#errorContainer p{margin:0;padding:0;font-size:11px;}</style></head><body><font face=\"Tahoma\" size=\"2\">");
                body.AppendLine("<style type=\"text/css\">#errorContainer {width:100%;margin-top:10px;padding:5px;background:#ffffcc;}#errorContainer div {margin:5px 0,padding:5px;}#errorContainer div, #errorContainer table{font-family:Tahoma;font-size:12px;background:#ffffcc;border:none;width:100%;border-collapse:collapse}#errorContainer table td{vertical-align:top;}#errorContainer p{margin:0;padding:0;font-size:11px;}</style>");
                body.AppendLine("<!-- ERROR -->");
                body.AppendLine("<b style=\"font-family:Tahoma;color:#f00;font-size:12px;\">Sorry, but an error was encountered on " + Environment.MachineName + " at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm") + "!</b>");
                body.AppendLine("<div id=\"errorContainer\">");

                body.AppendLine(GetContextInfo(true));

                body.AppendLine("<div><br /><font size=\"3\"><b>Exception:</b></font>");
                body.AppendLine(GetExceptionInfo(exception, true));
                body.AppendLine("<br /></div>");

                body.AppendLine(GetRequestInfo(true));

                if (!string.IsNullOrEmpty(extrainfo))
                {
                    body.AppendLine("<div>");
                    body.AppendLine("<br /><br /><b>Extra information that was provided:</b>");
                    body.AppendFormat("<div>{0}</div>", extrainfo.Replace("\r\n", "<br />").Replace("\r", "<br/>"));
                    body.AppendLine("</div>");
                }

                body.AppendLine("</div>");
                body.AppendLine("<div><font size=\"1\"><i>ErrorHelper version " + Version + " by SYSteam E AB, Douglas Nordfeldt</i></font></div>");
                body.AppendLine("</font></body></html>");

                var message = body.ToString();

                if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["ErrorLogToFiles"]) && ConfigurationManager.AppSettings["ErrorLogToFiles"].ToLower() == "true")
                    LogToFile(message);

                SendEmail(emails, message);
            }
            catch (Exception ex)
            {
                string content = "<b style=\"font-family:Tahoma;color:#f00;font-size:12px;\">Sorry, but an error was encountered in ErrorHelper.LogError().</b>" +
                    "<div id=\"errorContainer\">" + GetExceptionInfo(ex, true) + "</div>" +
                    "<b style=\"font-family:Tahoma;color:#f00;font-size:12px;\">Below is the source error</b>";

                content = body.ToString().Replace("<!-- ERROR -->", content);

                LogToFile(content);
            }
        }

        public static void LogErrorToTrace(Exception exception)
        {
            Trace.WriteLine(LogErrorToString(exception));
        }

        public static void LogErrorToTrace(Exception exception, string extrainfo)
        {
            Trace.WriteLine(LogErrorToString(exception, extrainfo));
        }

        public static string LogErrorToString(Exception exception)
        {
            return LogErrorToString(exception, string.Empty);
        }
        public static string LogErrorToString(Exception exception, string extrainfo)
        {
            if (exception == null && IsInWebContext)
                exception = HttpContext.Current.Server.GetLastError();

            if (exception == null)
                return string.Empty;

            var error = GetContextInfo(false);
            error += GetExceptionInfo(exception, false);
            error += GetRequestInfo(false);
            if (!string.IsNullOrEmpty(extrainfo))
                error += extrainfo;

            return error;
        }

        public static string GetContextInfo(bool html)
        {
            StringBuilder items = new StringBuilder();

            if (IsInWebContext)
            {
                HttpContext context = HttpContext.Current;

                items.AddValues("Machine Name", context.Server.MachineName + " (" + Environment.MachineName + " - " + Environment.OSVersion.VersionString + ")", html);
                items.AddValues("System started", GetPrettyDate(Environment.TickCount), html);
                items.AddValues(".Net Framework", Environment.Version.ToString(), html);
                items.AddValues("Server Ip", GetServerIp(context, System.Net.Sockets.AddressFamily.InterNetwork), html);
                items.AddValues("Server IpV6", GetServerIp(context, System.Net.Sockets.AddressFamily.InterNetworkV6), html);
                items.AddValues("CurrentCulture", Thread.CurrentThread.CurrentCulture, html);
                items.AddValues("CurrentUICulture", Thread.CurrentThread.CurrentUICulture, html);

                //EVRY Douglas[2012-10-04]: När IIS körs i integration mode finns det fall som context.Request != null ger en exception
                try
                {
                    if (context.Request != null)
                    {
                        items.AddValues("PhysicalPath", context.Request.PhysicalPath, html);
                        items.AddValues("AbsoluteUri", context.Request.Url.AbsoluteUri, html);

                        if (context.Request.ServerVariables["HTTP_X_REWRITE_URL"] != null && !string.IsNullOrEmpty(context.Request.ServerVariables["HTTP_X_REWRITE_URL"]))
                            items.AddValues("HTTP_X_REWRITE_URL", context.Request.ServerVariables["HTTP_X_REWRITE_URL"], html);

                        if (html)
                            items.AppendLine("</table><br /><table cellpadding=\"2\">");
                        else
                            items.AppendLine("");

                        items.AddValues("User Ip", context.Request.UserHostAddress, html);
                        items.AddValues("User Agent", context.Request.UserAgent, html);

                        if (context.Request.UserLanguages != null)
                        {
                            StringBuilder langs = new StringBuilder();
                            foreach (string lang in context.Request.UserLanguages)
                                langs.Append(lang + ", ");

                            items.AddValues("User Languages", langs, html);

                            //result.Append("<tr><td><b>User Languages:</b></td><td>");
                            //foreach (string lang in context.Request.UserLanguages)
                            //    result.Append(lang + ", ");
                            //result.Append("</td></tr>");
                        }

                        items.AddValues("RequestType", context.Request.RequestType, html);
                        items.AddValues("User Url Referrer", context.Request.UrlReferrer, html);
                    }
                }
                catch (HttpException)
                {
                    items.AddValues("PhysicalPath", HttpRuntime.AppDomainAppVirtualPath, html);
                }

                if (context.User != null)
                {
                    if (context.User.Identity != null)
                    {
                        items.AddValues("User.Identity.Name", context.User.Identity.Name, html);
                        items.AddValues("User.Identity.AuthenticationType", context.User.Identity.AuthenticationType, html);
                        items.AddValues("User.Identity.IsAuthenticated", context.User.Identity.IsAuthenticated, html);
                    }
                }
            }
            else
            {
                items.AddValues("Machine Name", Environment.MachineName + " (" + Environment.OSVersion.VersionString + ")", html);
                items.AddValues("System started", GetPrettyDate(Environment.TickCount), html);
                items.AddValues(".Net Framework", Environment.Version.ToString(), html);
                items.AddValues("UserName", Environment.UserName, html);
                items.AddValues("Server Ip", GetServerIp(System.Net.Sockets.AddressFamily.InterNetwork), html);
                items.AddValues("Server IpV6", GetServerIp(System.Net.Sockets.AddressFamily.InterNetworkV6), html);
                //items.AddValues("ProcessorCount", Environment.ProcessorCount, html);
                //items.AddValues("Memory in use", (Environment.WorkingSet / (1024 * 1024)), html);
                items.AddValues("CurrentCulture", Thread.CurrentThread.CurrentCulture, html);
                items.AddValues("CurrentUICulture", Thread.CurrentThread.CurrentUICulture, html);
            }

            StringBuilder result = new StringBuilder();
            result.AddSection("", items, html);

            return result.ToString();
        }
        public static string GetRequestInfo(bool html)
        {
            if (IsInWebContext)
            {
                HttpContext context = HttpContext.Current;
                StringBuilder result = new StringBuilder();
                StringBuilder items = new StringBuilder();

                //EVRY Douglas[2012-10-04]: När IIS körs i integration mode finns det fall som context.Request != null ger en exception
                try
                {
                    switch (context.Request.RequestType)
                    {
                        case "POST":
                            for (int i = 0; i < context.Request.Form.Count; i++)
                                items.AddValues(context.Request.Form.Keys[i], context.Request.Form[i], html);
                            break;
                        case "GET":
                            for (int i = 0; i < context.Request.QueryString.Count; i++)
                                items.AddValues(context.Request.QueryString.Keys[i], context.Request.QueryString[i], html);
                            break;
                    }

                    if (items.Length > 0)
                        result.AddSection(context.Request.RequestType, items, html);


                    if (context.Request.QueryString.Count > 0)
                    {
                        if (html)
                            result.AppendLine("<div><b>QueryString:</b> " + context.Request.QueryString + "</div><br />");
                        else
                            result.AddValues("QueryString", context.Request.QueryString, true);
                    }


                    if (context.Session != null)
                    {
                        items = new StringBuilder();

                        for (int i = 0; i < context.Session.Count; i++)
                            items.AddValues(context.Session.Keys[i], context.Session[i], html);

                        result.AddSection("Session", items, html);
                    }


                    if (context.Request.Cookies != null)
                    {
                        items = new StringBuilder();

                        for (int i = 0; i < context.Request.Cookies.Count; i++)
                        {
                            if (context.Request.Cookies[i].Values.Count > 0)
                            {
                                string values = string.Empty;
                                foreach (string key in context.Request.Cookies[i].Values)
                                {
                                    if (!string.IsNullOrEmpty(key))
                                        values += string.Concat("<b>", key, "</b>: ", context.Request.Cookies[i].Values[key], ",");
                                    else
                                        values += string.Concat(context.Request.Cookies[i].Values[key], ",");
                                }
                                items.AddValues(context.Request.Cookies.Keys[i], values, html);
                            }
                            else
                            {
                                items.AddValues(context.Request.Cookies.Keys[i], context.Request.Cookies[i].Value, html);
                            }
                        }

                        result.AddSection("Cookies", items, html);
                    }
                }
                catch (HttpException)
                { }

                return result.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
        private static string GetExceptionInfo(Exception exception, bool html)
        {
            StringBuilder items = new StringBuilder();

            try
            {
                if (html)
                {
                    items.AppendLine("<table>");
                }

                items.AddValues("Message", exception.Message, html);
                items.AddValues("Type", exception.GetType().ToString(), html);

                System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(exception, true);

                if (trace != null)
                {
                    System.Diagnostics.StackFrame[] frames = trace.GetFrames();

                    if (frames != null && frames.Length > 0 && trace.FrameCount > 0)
                    {
                        items.AddValues("File Name", frames[0].GetFileName(), html);
                        items.AddValues("Line Number", frames[0].GetFileLineNumber(), html);
                        items.AddValues("ColumnNumber", frames[0].GetFileColumnNumber(), html);

                        string par = string.Empty;

                        System.Reflection.MethodBase method = frames[0].GetMethod();

                        if (method != null)
                        {
                            System.Reflection.ParameterInfo[] parameters = method.GetParameters();

                            if (parameters != null && parameters.Length > 0)
                            {
                                for (int i = 0; i < parameters.Length; i++)
                                {
                                    par += parameters[i].ToString() + ", ";
                                }

                                par = par.Substring(0, par.Length - 2);
                            }
                        }
                        items.AddValues(method.MemberType + " Name", method.Name + "(" + par + ")", html);
                    }
                }

                if (exception.Source != null)
                    items.AddValues("Source", exception.Source, html);
                if (exception.TargetSite != null)
                    items.AddValues("TargetSite", exception.TargetSite, html);
                if (exception.StackTrace != null)
                {
                    if (html)
                        items.AddValues("StackTrace", "<p><font face=\"Tahoma\" size=\"1\">" + exception.StackTrace.Replace(Environment.NewLine, "<br />") + "</font></p>", html);
                    else
                        items.AddValues("StackTrace", exception.StackTrace, html);
                }

                if (html)
                {
                    items.AppendLine("</table>");
                }
            }
            catch (Exception ex)
            {
                return "Error in GetExceptionInfo(Exception exception) : " + ex.Message;
            }

            if (exception.InnerException != null)
            {
                if (html)
                {
                    items.AppendLine("<div><br /><font size=\"3\"><b>InnerException:</b></font>");
                    items.AppendLine(GetExceptionInfo(exception.InnerException, html));
                    items.AppendLine("</div>");
                }
                else
                {
                    items.AddSection("InnerException", GetExceptionInfo(exception.InnerException, html), html);
                }
            }

            return items.ToString();
        }

        private static string GetServerIp(System.Net.Sockets.AddressFamily addressFamily)
        {
            return GetServerIp(null, addressFamily);
        }
        private static string GetServerIp(HttpContext context, System.Net.Sockets.AddressFamily addressFamily)
        {
            List<string> addresses = new List<string>();
            string serverip = string.Empty;

            //EVRY Douglas[2012-10-04]: När IIS körs i integration mode finns det fall som context.Request != null ger en exception
            try
            {
                if (context != null && context.Request != null && context.Request.ServerVariables != null)
                    serverip = context.Request.ServerVariables["LOCAL_ADDR"];
            }
            catch (HttpException)
            { }

            string address;
            System.Net.IPAddress[] localIPs = System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName());

            for (int i = 0; i < localIPs.Length; i++)
            {
                if (localIPs[i].AddressFamily != addressFamily)
                    continue;

                address = localIPs[i].ToString();

                if (serverip.Equals(address))
                    addresses.Add("<b>" + address + "</b>");
                else
                    addresses.Add(address);
            }

            if (addresses.Count > 0)
                return String.Join(", ", addresses.ToArray());

            return string.Empty;
        }

        private static object LogLockObject = new object();
        public static void LogToFile(string content)
        {
            LogToFile(content, ".html");
        }
        /// <summary>
        /// Writes content to fil.
        /// </summary>
        /// <param name="content">The content to write to file.</param>
        /// <param name="file_extension">The extention of the file. Ex: .txt or .html</param>
        public static void LogToFile(string content, string file_extension)
        {
            string path = string.Empty;

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["ErrorLogFilesPath"]))
                path = ConfigurationManager.AppSettings["ErrorLogFilesPath"];
            else if (IsInWebContext)
                path = ERROR_FILE_PATH;
            else
                path = HostingEnvironment.MapPath(path);
            //file = "Errors";

            LogToFile(content, file_extension, path);
        }
        /// <summary>
        /// Writes content to fil.
        /// </summary>
        /// <param name="content">The content to write to file.</param>
        /// <param name="file_extension">The extention of the file. Ex: .txt, .log or .html</param>
        /// <param name="path">The path where to write file to.</param>
        public static void LogToFile(string content, string file_extension, string path)
        {
            if (string.IsNullOrEmpty(content) || string.IsNullOrEmpty(path))
                return;

            try
            {
                lock (LogLockObject)
                {
                    if (!Path.IsPathRooted(path))
                    {
                        if (IsInWebContext)
                            path = HttpContext.Current.Server.MapPath(path);
                        else
                            path = HostingEnvironment.MapPath(path);
                    }

                    path += "\\" + DateTime.Now.ToString("yyyy-MM-dd");

                    if (!Directory.Exists(path))
                        Directory.CreateDirectory(path);

                    File.AppendAllText(path + "\\" + DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.ffff") + file_extension, content);
                }
            }
            catch { }
        }

        private static void SendEmail(string[] emails, string message)
        {
            if ((emails == null || emails != null && emails.Length == 0) && !string.IsNullOrEmpty(ConfigurationManager.AppSettings["EmailError"]))
            {
                emails = ConfigurationManager.AppSettings["EmailError"].Split(',');
            }


            if ((emails == null || emails != null && emails.Length == 0) || string.IsNullOrEmpty(message))
                return;

            try
            {
                string sitename = string.Empty;
                if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["SiteName"]))
                    sitename = ConfigurationManager.AppSettings["SiteName"] + ": ";

                using (MailMessage mail = new MailMessage())
                {
                    mail.Subject = sitename + "Application Error on " + Environment.MachineName + " at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm");
                    mail.IsBodyHtml = true;
                    mail.Body = message;

                    for (int i = 0; i < emails.Length; i++)
                        if (!string.IsNullOrEmpty(emails[i]))
                            mail.To.Add(new System.Net.Mail.MailAddress(emails[i]));

                    if (mail.To.Count > 0)
                        new SmtpClient().Send(mail);
                    else
                        throw new ApplicationException("No email informed to send the errormessage");
                }
            }
            catch (Exception ex)
            {
                string content = "<b style=\"font-family:Tahoma;color:#f00;font-size:12px;\">Sorry, but an error was encountered in ErrorHelper.LogError().</b>" +
                    "<div id=\"errorContainer\">" + GetExceptionInfo(ex, true) + "</div>" +
                    "<b style=\"font-family:Tahoma;color:#f00;font-size:12px;\">Below is the source error</b>";

                content = message.Replace("<!-- ERROR -->", content);

                LogToFile(content);
            }
        }

        private static bool IsInWebContext
        {
            get { return HttpContext.Current != null; }
        }

        private static string GetPrettyDate(double milliseconds_ago)
        {
            DateTime date = DateTime.Now.AddMilliseconds(milliseconds_ago * -1);
            return date.GetPrettyDate();
        }
    }

    internal static class ErrorHelperExtensions
    {
        public static StringBuilder AddValues(this StringBuilder sb, string name, object value, bool html)
        {
            if (html)
            {
                return sb.AppendFormat("<tr><td style=\"white-space: nowrap;\" nowrap=\"nowrap\"><b>{0}:</b></td><td>{1}</td></tr>\r\n", name, value);
            }
            else
            {
                string tabs = "";
                for (int i = 22; i >= 0; i -= 4)
                {
                    if (name.Length < i)
                        tabs += "\t";
                    else
                        break;
                }

                //if (name.Length < 22)
                //    tabs += "\t";
                //if (name.Length < 18)
                //    tabs += "\t";
                //if (name.Length < 14)
                //    tabs += "\t";
                //if (name.Length < 10)
                //    tabs += "\t";
                //if (name.Length < 6)
                //    tabs += "\t";
                //if (name.Length < 2)
                //    tabs += "\t";

                return sb.AppendLine(name + ": " + tabs + value);
            }
        }
        public static StringBuilder AddSection(this StringBuilder sb, string title, object rows, bool html)
        {
            if (!string.IsNullOrEmpty(title))
            {
                if (html)
                    title = string.Format("<b>{0}:</b>", title);
                else
                    title = title + ":" + Environment.NewLine;
            }

            if (html)
                return sb.AppendFormat("<div>{0}<table cellpadding=\"2\">{1}</table></div><br />\r\n", title, rows);
            else
                return sb.AppendFormat(Environment.NewLine + "-----------------------------------" + Environment.NewLine + "{0}{1}" + Environment.NewLine, title, rows);
        }
        public static string GetPrettyDate(this DateTime date)
        {
            TimeSpan diff = DateTime.Now - date;

            if (diff.Seconds <= 0)
                return "not yet";

            string prettyDate = string.Empty;
            System.Collections.Generic.List<string> dates = new System.Collections.Generic.List<string>();

            if (diff.Days > 30)
                dates.Add(diff.Days / 30 + " month" + (diff.Days / 30 >= 2 ? "s" : ""));
            //if (Diff.Days > 7)
            //    dates.Add(Diff.Days / 7 + " week" + (Diff.Days / 7 >= 2 ? "s" : ""));
            if (diff.Days >= 1)
                dates.Add(diff.Days + " day" + (diff.Days >= 2 ? "s" : ""));
            if (diff.Hours >= 1)
                dates.Add(diff.Hours + " hour" + (diff.Hours >= 2 ? "s" : ""));
            if (diff.Minutes >= 1)
                dates.Add(diff.Minutes + " minute" + (diff.Minutes >= 2 ? "s" : ""));

            string result = string.Empty;

            for (int i = 0; i < dates.Count; i++)
            {
                if (i > 0)
                    result += ", ";
                result += dates[i];
            }

            if (!string.IsNullOrEmpty(result))
                result += " and ";

            result += diff.Seconds + " second" + (diff.Seconds >= 2 ? "s" : "");

            return result + " ago";
        }
    }
}
